#include <stdint.h>

#include "ffmpeg.h"
#include "cmdutils.h"

#include "libavformat/avformat.h"

#include "libavcodec/avcodec.h"

#include "libavfilter/avfilter.h"

#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/avutil.h"
#include "libavutil/error.h"
#include "libavutil/channel_layout.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/fifo.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/parseutils.h"
#include "libavutil/pixdesc.h"
#include "libavutil/pixfmt.h"

#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass"

#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st) { \
    int i, ret;\
    for (i = 0; i < o->nb_ ## name; i++) { \
        char *spec = o->name[i].specifier; \
        if ((ret = check_stream_specifier(fmtctx, st, spec)) > 0) { \
            outvar = o->name[i].u.type; \
        } else if (ret < 0) { \
            exit_program(1); \
        } \
    } \
}

#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype) { \
    int i;\
    for (i = 0; i < o->nb_ ## name; i++) { \
        char *spec = o->name[i].specifier; \
        if (!strcmp(spec, mediatype)) { \
            outvar = o->name[i].u.type; \
        } \
    } \
}

void uninit_options(OptionsContext* o) {
    const OptionDef* po = options;
    int i;

    /* all OPT_SPEC and OPT_STRING can be freed in generic way */
    while (po->name) {
        void* dst = (uint8_t*)o + po->u.off;
        if (po->flags & OPT_SPEC) {
            SpecifierOpt** so = dst;
            int i, *count = (int*)(so + 1);
            for (i = 0; i < *count; i++) {
                av_freep(&(*so)[i].specifier);
                if (po->flags & OPT_STRING) {
                    av_freep(&(*so)[i].u.str);
                }
            }
            av_freep(so);
            *count = 0;
        } else if (po->flags & OPT_OFFSET && po->flags & OPT_STRING) {
            av_freep(dst);
        }
        po++;
    }

    for (i = 0; i < o->nb_stream_maps; i++) {
        av_freep(&o->stream_maps[i].linklabel);
    }
    av_freep(&o->stream_maps);
    av_freep(&o->audio_channel_maps);
    av_freep(&o->streamid_map);
    av_freep(&o->attachments);
}

void init_options(OptionsContext* o) {
    memset(o, 0, sizeof(*o));

    o->stop_time = INT64_MAX;
    o->mux_max_delay  = 0.7;
    o->start_time     = AV_NOPTS_VALUE;
    o->start_time_eof = AV_NOPTS_VALUE;
    o->recording_time = INT64_MAX;
    o->limit_filesize = UINT64_MAX;
    o->chapters_input_file = INT_MAX;
    o->accurate_seek  = 1;
}

int show_hwaccels(void* optctx, const char* opt, const char* arg) {
    enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
    int i;

    printf("Hardware acceleration methods:\n");
    while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
        printf("%s\n", av_hwdevice_get_type_name(type));
    }
    for (i = 0; hwaccels[i].name; i++) {
        printf("%s\n", hwaccels[i].name);
    }
    printf("\n");
    return 0;
}

/* return a copy of the input with the stream specifiers removed from the keys */
AVDictionary* strip_specifiers(AVDictionary* dict) {
    AVDictionaryEntry* e = NULL;
    AVDictionary*    ret = NULL;

    while ((e = av_dict_get(dict, "", e, AV_DICT_IGNORE_SUFFIX))) {
        char* p = strchr(e->key, ':');
        if (p) {
            *p = 0;
        }
        av_dict_set(&ret, e->key, e->value, 0);
        if (p) {
            *p = ':';
        }
    }
    return ret;
}

static const AVOption opts[] = {
    { "abort_on", NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, INT64_MAX, .unit = "flags" },
    { "empty_output", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT }, .unit = "flags" },
    { NULL },
};
static const AVClass avclass = { "", av_default_item_name, opts, LIBAVUTIL_VERSION_INT };
const AVClass* pclass = &avclass;

int opt_abort_on(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return av_opt_eval_flags(&pclass, &opts[0], arg, &(o.abort_on_flags));
}

int opt_sameq(void* optctx, const char* opt, const char* arg) {
    av_log(NULL, AV_LOG_ERROR, "Option '%s' was removed. "
           "If you are looking for an option to preserve the quality (which is not "
           "what -%s was for), use -qscale 0 or an equivalent quality factor option.\n",
           opt, opt);
    return AVERROR(EINVAL);
}

int opt_video_channel(void* optctx, const char* opt, const char* arg) {
    av_log(NULL, AV_LOG_WARNING, "This option is deprecated, use -channel.\n");
    return opt_default(optctx, "channel", arg);
}

int opt_video_standard(void* optctx, const char* opt, const char* arg) {
    av_log(NULL, AV_LOG_WARNING, "This option is deprecated, use -standard.\n");
    return opt_default(optctx, "standard", arg);
}

int opt_audio_codec(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "codec:a", arg, options);
}

int opt_video_codec(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "codec:v", arg, options);
}

int opt_subtitle_codec(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "codec:s", arg, options);
}

int opt_data_codec(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "codec:d", arg, options);
}

int opt_map(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    StreamMap* m = NULL;
    int i, negative = 0, file_idx, disabled = 0;
    int sync_file_idx = -1, sync_stream_idx = 0;
    char* p = NULL, *sync = NULL;
    char* map = NULL;
    char* allow_unused = NULL;

    if (*arg == '-') {
        negative = 1;
        arg++;
    }
    map = av_strdup(arg);
    if (!map) {
        return AVERROR(ENOMEM);
    }
    /* parse sync stream first, just pick first matching stream */
    if (sync = strchr(map, ',')) {
        *sync = 0;
        sync_file_idx = strtol(sync + 1, &sync, 0);
        if (sync_file_idx >= nb_input_files || sync_file_idx < 0) {
            av_log(NULL, AV_LOG_FATAL, "Invalid sync file index: %d.\n", sync_file_idx);
            exit_program(1);
        }
        if (*sync) {
            sync++;
        }
        for (i = 0; i < input_files[sync_file_idx]->nb_streams; i++) {
            if (check_stream_specifier(input_files[sync_file_idx]->ctx, input_files[sync_file_idx]->ctx->streams[i], sync) == 1) {
                sync_stream_idx = i;
                break;
            }
        }
        if (i == input_files[sync_file_idx]->nb_streams) {
            av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s does not match any streams.\n", arg);
            exit_program(1);
        }
        if (input_streams[input_files[sync_file_idx]->ist_index + sync_stream_idx]->user_set_discard == AVDISCARD_ALL) {
            av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s matches a disabled input stream.\n", arg);
            exit_program(1);
        }
    }


    if (map[0] == '[') {
        /* this mapping refers to lavfi output */
        const char* c = map + 1;
        GROW_ARRAY(o->stream_maps, o->nb_stream_maps);
        m = &o->stream_maps[o->nb_stream_maps - 1];
        m->linklabel = av_get_token(&c, "]");
        if (!m->linklabel) {
            av_log(NULL, AV_LOG_ERROR, "Invalid output link label: %s.\n", map);
            exit_program(1);
        }
    } else {
        allow_unused = strchr(map, '?');
        if (allow_unused != NULL) {
            * allow_unused = 0;
        }
        file_idx = strtol(map, &p, 0);
        if (file_idx >= nb_input_files || file_idx < 0) {
            av_log(NULL, AV_LOG_FATAL, "Invalid input file index: %d.\n", file_idx);
            exit_program(1);
        }
        if (negative)
            /* disable some already defined maps */
            for (i = 0; i < o->nb_stream_maps; i++) {
                m = &o->stream_maps[i];
                if (file_idx == m->file_index &&
                        check_stream_specifier(input_files[m->file_index]->ctx,
                                               input_files[m->file_index]->ctx->streams[m->stream_index],
                                               *p == ':' ? p + 1 : p) > 0)
                    m->disabled = 1;
            } else
            for (i = 0; i < input_files[file_idx]->nb_streams; i++) {
                if (check_stream_specifier(input_files[file_idx]->ctx, input_files[file_idx]->ctx->streams[i], *p == ':' ? p + 1 : p) <= 0) {
                    continue;
                }
                if (input_streams[input_files[file_idx]->ist_index + i]->user_set_discard == AVDISCARD_ALL) {
                    disabled = 1;
                    continue;
                }
                GROW_ARRAY(o->stream_maps, o->nb_stream_maps);
                m = &o->stream_maps[o->nb_stream_maps - 1];

                m->file_index   = file_idx;
                m->stream_index = i;

                if (sync_file_idx >= 0) {
                    m->sync_file_index   = sync_file_idx;
                    m->sync_stream_index = sync_stream_idx;
                } else {
                    m->sync_file_index   = file_idx;
                    m->sync_stream_index = i;
                }
            }
    }

    if (!m) {
        if (allow_unused) {
            av_log(NULL, AV_LOG_VERBOSE, "Stream map '%s' matches no streams; ignoring.\n", arg);
        } else if (disabled) {
            av_log(NULL, AV_LOG_FATAL, "Stream map '%s' matches disabled streams.\n"
                   "To ignore this, add a trailing '?' to the map.\n", arg);
            exit_program(1);
        } else {
            av_log(NULL, AV_LOG_FATAL, "Stream map '%s' matches no streams.\n"
                   "To ignore this, add a trailing '?' to the map.\n", arg);
            exit_program(1);
        }
    }

    av_freep(&map);
    return 0;
}

int opt_attach(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    GROW_ARRAY(o->attachments, o->nb_attachments);
    o->attachments[o->nb_attachments - 1] = arg;
    return 0;
}

int opt_map_channel(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    int n;
    AVStream* st;
    AudioChannelMap* m;
    char* allow_unused;
    char* mapchan;
    mapchan = av_strdup(arg);
    if (!mapchan) {
        return AVERROR(ENOMEM);
    }
    GROW_ARRAY(o->audio_channel_maps, o->nb_audio_channel_maps);
    m = &o->audio_channel_maps[o->nb_audio_channel_maps - 1];

    /* muted channel syntax */
    n = sscanf(arg, "%d:%d.%d", &m->channel_idx, &m->ofile_idx, &m->ostream_idx);
    if ((n == 1 || n == 3) && m->channel_idx == -1) {
        m->file_idx = m->stream_idx = -1;
        if (n == 1) {
            m->ofile_idx = m->ostream_idx = -1;
        }
        av_free(mapchan);
        return 0;
    }

    /* normal syntax */
    n = sscanf(arg, "%d.%d.%d:%d.%d", &m->file_idx,  &m->stream_idx, &m->channel_idx, &m->ofile_idx, &m->ostream_idx);

    if (n != 3 && n != 5) {
        av_log(NULL, AV_LOG_FATAL, "Syntax error, mapchan usage: [file.stream.channel|-1][:syncfile:syncstream]\n");
        exit_program(1);
    }

    if (n != 5) { // only file.stream.channel specified
        m->ofile_idx = m->ostream_idx = -1;
    }
    /* check input */
    if (m->file_idx < 0 || m->file_idx >= nb_input_files) {
        av_log(NULL, AV_LOG_FATAL, "mapchan: invalid input file index: %d\n", m->file_idx);
        exit_program(1);
    }
    if (m->stream_idx < 0 || m->stream_idx >= input_files[m->file_idx]->nb_streams) {
        av_log(NULL, AV_LOG_FATAL, "mapchan: invalid input file stream index #%d.%d\n", m->file_idx, m->stream_idx);
        exit_program(1);
    }
    st = input_files[m->file_idx]->ctx->streams[m->stream_idx];
    if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
        av_log(NULL, AV_LOG_FATAL, "mapchan: stream #%d.%d is not an audio stream.\n", m->file_idx, m->stream_idx);
        exit_program(1);
    }
    /* allow trailing ? to map_channel */
    allow_unused = strchr(mapchan, '?');
    if (allow_unused != NULL) {
        *allow_unused = 0;
    }
    if (m->channel_idx < 0 || m->channel_idx >= st->codecpar->channels ||
            input_streams[input_files[m->file_idx]->ist_index + m->stream_idx]->user_set_discard == AVDISCARD_ALL) {
        if (allow_unused) {
            av_log(NULL, AV_LOG_VERBOSE, "mapchan: invalid audio channel #%d.%d.%d\n", m->file_idx, m->stream_idx, m->channel_idx);
        } else {
            av_log(NULL, AV_LOG_FATAL,  "mapchan: invalid audio channel #%d.%d.%d\n To ignore this, add a trailing '?' to the map_channel.\n", m->file_idx, m->stream_idx, m->channel_idx);
            exit_program(1);
        }
    }
    av_free(mapchan);
    return 0;
}

int opt_sdp_file(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    av_free(sdp_filename);
    sdp_filename = av_strdup(arg);
    return 0;
}

#if CONFIG_VAAPI
int opt_vaapi_device(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    HWDevice* dev;
    const char* prefix = "vaapi:";
    char* tmp;
    int err;
    tmp = av_asprintf("%s%s", prefix, arg);
    if (!tmp) {
        return AVERROR(ENOMEM);
    }
    err = hw_device_init_from_string(tmp, &dev);
    av_free(tmp);
    if (err < 0) {
        return err;
    }
    hw_device_ctx = av_buffer_ref(dev->device_ref);
    if (!hw_device_ctx) {
        return AVERROR(ENOMEM);
    }
    return 0;
}
#endif

int opt_init_hw_device(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    if (!strcmp(arg, "list")) {
        enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
        printf("Supported hardware device types:\n");
        while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
            printf("%s\n", av_hwdevice_get_type_name(type));
        }
        printf("\n");
        exit_program(0);
    } else {
        return hw_device_init_from_string(arg, NULL);
    }
}

int opt_filter_hw_device(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    if (filter_hw_device) {
        av_log(NULL, AV_LOG_ERROR, "Only one filter device can be used.\n");
        return AVERROR(EINVAL);
    }
    filter_hw_device = hw_device_get_by_name(arg);
    if (!filter_hw_device) {
        av_log(NULL, AV_LOG_ERROR, "Invalid filter device %s.\n", arg);
        return AVERROR(EINVAL);
    }
    return 0;
}

/**
 * Parse a metadata specifier passed as 'arg' parameter.
 * @param arg  metadata string to parse
 * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram)
 * @param index for type c/p, chapter/program index is written here
 * @param stream_spec for type s, the stream specifier is written here
 */
void parse_meta_type(char* arg, char* type, int* index, const char** stream_spec) {
    if (*arg) {
        *type = *arg;
        switch (*arg) {
            case 'g':
                break;
            case 's': {
                if (*(++arg) && *arg != ':') {
                    av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg);
                    exit_program(1);
                }
                *stream_spec = *arg == ':' ? arg + 1 : "";
            } break;
            case 'c':
            case 'p': {
                if (*(++arg) == ':') {
                    *index = strtol(++arg, NULL, 0);
                }
            } break;
            default:
                av_log(NULL, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg);
                exit_program(1);
        }
    } else {
        *type = 'g';
    }
}

int copy_metadata(char* outspec, char* inspec, AVFormatContext* oc, AVFormatContext* ic, OptionsContext* o) {
    AVDictionary** meta_in = NULL;
    AVDictionary** meta_out = NULL;
    int i, ret = 0;
    char type_in, type_out;
    const char* istream_spec = NULL, *ostream_spec = NULL;
    int idx_in = 0, idx_out = 0;

    parse_meta_type(inspec,  &type_in,  &idx_in,  &istream_spec);
    parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec);

    if (!ic) {
        if (type_out == 'g' || !*outspec) {
            o->metadata_global_manual = 1;
        }
        if (type_out == 's' || !*outspec) {
            o->metadata_streams_manual = 1;
        }
        if (type_out == 'c' || !*outspec) {
            o->metadata_chapters_manual = 1;
        }
        return 0;
    }

    if (type_in == 'g' || type_out == 'g') {
        o->metadata_global_manual = 1;
    }
    if (type_in == 's' || type_out == 's') {
        o->metadata_streams_manual = 1;
    }
    if (type_in == 'c' || type_out == 'c') {
        o->metadata_chapters_manual = 1;
    }
    /* ic is NULL when just disabling automatic mappings */
    if (!ic) {
        return 0;
    }
#define METADATA_CHECK_INDEX(index, nb_elems, desc)\
    if ((index) < 0 || (index) >= (nb_elems)) {\
        av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n", (desc), (index));\
        exit_program(1);\
    }

#define SET_DICT(type, meta, context, index)\
        switch (type) {\
        case 'g':\
            meta = &context->metadata;\
            break;\
        case 'c':\
            METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\
            meta = &context->chapters[index]->metadata;\
            break;\
        case 'p':\
            METADATA_CHECK_INDEX(index, context->nb_programs, "program")\
            meta = &context->programs[index]->metadata;\
            break;\
        case 's':\
            break; /* handled separately below */ \
        default: av_assert0(0);\
        }\

    SET_DICT(type_in, meta_in, ic, idx_in);
    SET_DICT(type_out, meta_out, oc, idx_out);

    /* for input streams choose first matching stream */
    if (type_in == 's') {
        for (i = 0; i < ic->nb_streams; i++) {
            if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) {
                meta_in = &ic->streams[i]->metadata;
                break;
            } else if (ret < 0) {
                exit_program(1);
            }
        }
        if (!meta_in) {
            av_log(NULL, AV_LOG_FATAL, "Stream specifier %s does not match  any streams.\n", istream_spec);
            exit_program(1);
        }
    }

    if (type_out == 's') {
        for (i = 0; i < oc->nb_streams; i++) {
            if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) {
                meta_out = &oc->streams[i]->metadata;
                av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE);
            } else if (ret < 0) {
                exit_program(1);
            }
        }
    } else {
        av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE);
    }
    return 0;
}

int opt_recording_timestamp(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    char buf[128];
    int64_t recording_timestamp = parse_time_or_die(opt, arg, 0) / 1E6;
    struct tm time = *gmtime((time_t*)&recording_timestamp);
    if (!strftime(buf, sizeof(buf), "creation_time=%Y-%m-%dT%H:%M:%S%z", &time)) {
        return -1;
    }
    parse_option(o, "metadata", buf, options);

    av_log(NULL, AV_LOG_WARNING, "%s is deprecated, set the 'creation_time' metadata tag instead.\n", opt);
    return 0;
}

AVCodec* find_codec_or_die(const char* name, enum AVMediaType type, int encoder) {
    const AVCodecDescriptor* desc;
    const char* codec_string = encoder ? "encoder" : "decoder";
    AVCodec* codec;

    codec = encoder ? avcodec_find_encoder_by_name(name) : avcodec_find_decoder_by_name(name);
    if (!codec && (desc = avcodec_descriptor_get_by_name(name))) {
        codec = encoder ? avcodec_find_encoder(desc->id) : avcodec_find_decoder(desc->id);
        if (codec) {
            av_log(NULL, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", codec_string, codec->name, desc->name);
        }
    }
    if (!codec) {
        av_log(NULL, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name);
        exit_program(1);
    }
    if (codec->type != type) {
        av_log(NULL, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name);
        exit_program(1);
    }
    return codec;
}

AVCodec* choose_decoder(OptionsContext* o, AVFormatContext* s, AVStream* st) {
    char* codec_name = NULL;
    MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st);
    if (codec_name) {
        AVCodec* codec = find_codec_or_die(codec_name, st->codecpar->codec_type, 0);
        st->codecpar->codec_id = codec->id;
        return codec;
    } else {
        return avcodec_find_decoder(st->codecpar->codec_id);
    }
}

/* Add all the streams from the given input file to the global
 * list of input streams. */
void add_input_streams(OptionsContext* o, AVFormatContext* ic) {
    int i, ret;
    for (i = 0; i < ic->nb_streams; i++) {
        AVStream* st = ic->streams[i];
        AVCodecParameters* par = st->codecpar;
        InputStream* ist = av_mallocz(sizeof(*ist));
        char* framerate = NULL, *hwaccel_device = NULL;
        const char* hwaccel = NULL;
        char* hwaccel_output_format = NULL;
        char* codec_tag = NULL;
        char* next;
        char* discard_str = NULL;
        const AVClass* cc = avcodec_get_class();
        const AVOption* discard_opt = av_opt_find(&cc, "skip_frame", NULL, 0, 0);

        if (!ist) {
            exit_program(1);
        }
        GROW_ARRAY(input_streams, nb_input_streams);
        input_streams[nb_input_streams - 1] = ist;

        ist->st = st;
        ist->file_index = nb_input_files;
        ist->discard = 1;
        st->discard  = AVDISCARD_ALL;
        ist->nb_samples = 0;
        ist->min_pts = INT64_MAX;
        ist->max_pts = INT64_MIN;

        ist->ts_scale = 1.0;
        MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st);

        ist->autorotate = 1;
        MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st);

        MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st);
        if (codec_tag) {
            uint32_t tag = strtol(codec_tag, &next, 0);
            if (*next) {
                tag = AV_RL32(codec_tag);
            }
            st->codecpar->codec_tag = tag;
        }

        ist->dec = choose_decoder(o, ic, st);
        ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec);

        ist->reinit_filters = -1;
        MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st);

        MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st);
        ist->user_set_discard = AVDISCARD_NONE;

        if ((o->video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) ||
                (o->audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ||
                (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) ||
                (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)) {
            ist->user_set_discard = AVDISCARD_ALL;
        }
        if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", discard_str);
            exit_program(1);
        }

        ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE;

        ist->dec_ctx = avcodec_alloc_context3(ist->dec);
        if (!ist->dec_ctx) {
            av_log(NULL, AV_LOG_ERROR, "Error allocating the decoder context.\n");
            exit_program(1);
        }

        ret = avcodec_parameters_to_context(ist->dec_ctx, par);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n");
            exit_program(1);
        }

        if (o->bitexact) {
            ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT;
        }
        switch (par->codec_type) {
            case AVMEDIA_TYPE_VIDEO: {
                if (!ist->dec) {
                    ist->dec = avcodec_find_decoder(par->codec_id);
                }
#if FF_API_LOWRES
                if (st->codec->lowres) {
                    ist->dec_ctx->lowres = st->codec->lowres;
                    ist->dec_ctx->width  = st->codec->width;
                    ist->dec_ctx->height = st->codec->height;
                    ist->dec_ctx->coded_width  = st->codec->coded_width;
                    ist->dec_ctx->coded_height = st->codec->coded_height;
                }
#endif
                // avformat_find_stream_info() doesn't set this for us anymore.
                ist->dec_ctx->framerate = st->avg_frame_rate;

                MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st);
                if (framerate && av_parse_video_rate(&ist->framerate, framerate) < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", framerate);
                    exit_program(1);
                }

                ist->top_field_first = -1;
                MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st);

                MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st);
                if (hwaccel) {
                    // The NVDEC hwaccels use a CUDA device, so remap the name here.
                    if (!strcmp(hwaccel, "nvdec")) {
                        hwaccel = "cuda";
                    }
                    if (!strcmp(hwaccel, "none")) {
                        ist->hwaccel_id = HWACCEL_NONE;
                    } else if (!strcmp(hwaccel, "auto")) {
                        ist->hwaccel_id = HWACCEL_AUTO;
                    } else {
                        enum AVHWDeviceType type;
                        int i;
                        for (i = 0; hwaccels[i].name; i++) {
                            if (!strcmp(hwaccels[i].name, hwaccel)) {
                                ist->hwaccel_id = hwaccels[i].id;
                                break;
                            }
                        }
                        if (!ist->hwaccel_id) {
                            type = av_hwdevice_find_type_by_name(hwaccel);
                            if (type != AV_HWDEVICE_TYPE_NONE) {
                                ist->hwaccel_id = HWACCEL_GENERIC;
                                ist->hwaccel_device_type = type;
                            }
                        }
                        if (!ist->hwaccel_id) {
                            av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n",
                                   hwaccel);
                            av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: ");
                            type = AV_HWDEVICE_TYPE_NONE;
                            while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
                                av_log(NULL, AV_LOG_FATAL, "%s ", av_hwdevice_get_type_name(type));
                            }
                            for (i = 0; hwaccels[i].name; i++) {
                                av_log(NULL, AV_LOG_FATAL, "%s ", hwaccels[i].name);
                            }
                            av_log(NULL, AV_LOG_FATAL, "\n");
                            exit_program(1);
                        }
                    }
                }

                MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st);
                if (hwaccel_device) {
                    ist->hwaccel_device = av_strdup(hwaccel_device);
                    if (!ist->hwaccel_device) {
                        exit_program(1);
                    }
                }

                MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, hwaccel_output_format, ic, st);
                if (hwaccel_output_format) {
                    ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format);
                    if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) {
                        av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output format: %s", hwaccel_output_format);
                    }
                } else {
                    ist->hwaccel_output_format = AV_PIX_FMT_NONE;
                }

                ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE;
            } break;
            case AVMEDIA_TYPE_AUDIO: {
                ist->guess_layout_max = INT_MAX;
                MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st);
                guess_input_channel_layout(ist);
            } break;
            case AVMEDIA_TYPE_DATA:
            case AVMEDIA_TYPE_SUBTITLE: {
                char* canvas_size = NULL;
                if (!ist->dec)
                    ist->dec = avcodec_find_decoder(par->codec_id);
                MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st);
                MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st);
                if (canvas_size &&
                        av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) {
                    av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size);
                    exit_program(1);
                }
            } break;
            case AVMEDIA_TYPE_ATTACHMENT:
            case AVMEDIA_TYPE_UNKNOWN:
                break;
            default:
                abort();
        }

        ret = avcodec_parameters_from_context(par, ist->dec_ctx);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n");
            exit_program(1);
        }
    }
}

void dump_attachment(AVStream* st, const char* filename) {
    int ret;
    AVIOContext* out = NULL;
    AVDictionaryEntry* e;

    if (!st->codecpar->extradata_size) {
        av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", nb_input_files - 1, st->index);
        return;
    }
    if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) {
        filename = e->value;
    }
    if (!*filename) {
        av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag in stream #%d:%d.\n", nb_input_files - 1, st->index);
        exit_program(1);
    }

    if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) {
        av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", filename);
        exit_program(1);
    }

    avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size);
    avio_flush(out);
    avio_close(out);
}

static int open_input_file(OptionsContext* o, const char* filename) {
    InputFile* f;
    AVFormatContext* ic;
    AVInputFormat* file_iformat = NULL;
    int err, i, ret;
    int64_t timestamp;
    AVDictionary* unused_opts = NULL;
    AVDictionaryEntry* e = NULL;
    char*    video_codec_name = NULL;
    char*    audio_codec_name = NULL;
    char* subtitle_codec_name = NULL;
    char*     data_codec_name = NULL;
    int scan_all_pmts_set = 0;

    if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) {
        o->stop_time = INT64_MAX;
        av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n");
    }

    if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) {
        int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time;
        if (o->stop_time <= start_time) {
            av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n");
            exit_program(1);
        } else {
            o->recording_time = o->stop_time - start_time;
        }
    }

    if (o->format) {
        if (!(file_iformat = av_find_input_format(o->format))) {
            av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format);
            exit_program(1);
        }
    }

    if (!strcmp(filename, "-")) {
        filename = "pipe:";
    }

    /* get default parameters from command line */
    ic = avformat_alloc_context();
    if (!ic) {
        print_error(filename, AVERROR(ENOMEM));
        exit_program(1);
    }
    if (o->nb_audio_sample_rate) {
        av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0);
    }
    if (o->nb_audio_channels) {
        /* because we set audio_channels based on both the "ac" and
         * "channel_layout" options, we need to check that the specified
         * demuxer actually has the "channels" option before setting it */
        if (file_iformat && file_iformat->priv_class && av_opt_find(&file_iformat->priv_class, "channels", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)) {
            av_dict_set_int(&o->g->format_opts, "channels", o->audio_channels[o->nb_audio_channels - 1].u.i, 0);
        }
    }
    if (o->nb_frame_rates) {
        /* set the format-level framerate option;
         * this is important for video grabbers, e.g. x11 */
        if (file_iformat && file_iformat->priv_class && av_opt_find(&file_iformat->priv_class, "framerate", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)) {
            av_dict_set(&o->g->format_opts, "framerate", o->frame_rates[o->nb_frame_rates - 1].u.str, 0);
        }
    }
    if (o->nb_frame_sizes) {
        av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0);
    }
    if (o->nb_frame_pix_fmts) {
        av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0);
    }
    MATCH_PER_TYPE_OPT(codec_names, str,    video_codec_name, ic, "v");
    MATCH_PER_TYPE_OPT(codec_names, str,    audio_codec_name, ic, "a");
    MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s");
    MATCH_PER_TYPE_OPT(codec_names, str,     data_codec_name, ic, "d");

    if (video_codec_name) {
        ic->video_codec    = find_codec_or_die(video_codec_name, AVMEDIA_TYPE_VIDEO, 0);
    }
    if (audio_codec_name) {
        ic->audio_codec    = find_codec_or_die(audio_codec_name, AVMEDIA_TYPE_AUDIO, 0);
    }
    if (subtitle_codec_name) {
        ic->subtitle_codec = find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0);
    }
    if (data_codec_name) {
        ic->data_codec     = find_codec_or_die(data_codec_name, AVMEDIA_TYPE_DATA, 0);
    }
    ic->video_codec_id     = video_codec_name    ? ic->video_codec->id    : AV_CODEC_ID_NONE;
    ic->audio_codec_id     = audio_codec_name    ? ic->audio_codec->id    : AV_CODEC_ID_NONE;
    ic->subtitle_codec_id  = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE;
    ic->data_codec_id      = data_codec_name     ? ic->data_codec->id     : AV_CODEC_ID_NONE;

    ic->flags |= AVFMT_FLAG_NONBLOCK;
    if (o->bitexact) {
        ic->flags |= AVFMT_FLAG_BITEXACT;
    }
    ic->interrupt_callback = int_cb;

    if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
        av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
        scan_all_pmts_set = 1;
    }
    /* open the input file with generic avformat function */
    err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts);
    if (err < 0) {
        print_error(filename, err);
        if (err == AVERROR_PROTOCOL_NOT_FOUND) {
            av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename);
        }
        exit_program(1);
    }
    if (scan_all_pmts_set) {
        av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
    }
    remove_avoptions(&o->g->format_opts, o->g->codec_opts);
    assert_avoptions(o->g->format_opts);

    /* apply forced codec ids */
    for (i = 0; i < ic->nb_streams; i++) {
        choose_decoder(o, ic, ic->streams[i]);
    }
    if (find_stream_info) {
        AVDictionary** opts = setup_find_stream_info_opts(ic, o->g->codec_opts);
        int orig_nb_streams = ic->nb_streams;

        /* If not enough info to get the stream parameters, we decode the
           first frames to get it. (used in mpeg case for example) */
        ret = avformat_find_stream_info(ic, opts);

        for (i = 0; i < orig_nb_streams; i++) {
            av_dict_free(&opts[i]);
        }
        av_freep(&opts);

        if (ret < 0) {
            av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename);
            if (ic->nb_streams == 0) {
                avformat_close_input(&ic);
                exit_program(1);
            }
        }
    }

    if (o->start_time != AV_NOPTS_VALUE && o->start_time_eof != AV_NOPTS_VALUE) {
        av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename);
        o->start_time_eof = AV_NOPTS_VALUE;
    }

    if (o->start_time_eof != AV_NOPTS_VALUE) {
        if (o->start_time_eof >= 0) {
            av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n");
            exit_program(1);
        }
        if (ic->duration > 0) {
            o->start_time = o->start_time_eof + ic->duration;
            if (o->start_time < 0) {
                av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename);
                o->start_time = AV_NOPTS_VALUE;
            }
        } else {
            av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename);
        }
    }
    timestamp = (o->start_time == AV_NOPTS_VALUE) ? 0 : o->start_time;
    /* add the stream start time */
    if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) {
        timestamp += ic->start_time;
    }

    /* if seeking requested, we execute it */
    if (o->start_time != AV_NOPTS_VALUE) {
        int64_t seek_timestamp = timestamp;
        if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) {
            int dts_heuristic = 0;
            for (i = 0; i < ic->nb_streams; i++) {
                const AVCodecParameters* par = ic->streams[i]->codecpar;
                if (par->video_delay) {
                    dts_heuristic = 1;
                    break;
                }
            }
            if (dts_heuristic) {
                seek_timestamp -= 3 * AV_TIME_BASE / 23;
            }
        }
        ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0);
        if (ret < 0) {
            av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", filename, (double)timestamp / AV_TIME_BASE);
        }
    }

    /* update the current parameters so that they match the one of the input stream */
    add_input_streams(o, ic);

    /* dump the file content */
    av_dump_format(ic, nb_input_files, filename, 0);

    GROW_ARRAY(input_files, nb_input_files);
    f = av_mallocz(sizeof(*f));
    if (!f) {
        exit_program(1);
    }
    input_files[nb_input_files - 1] = f;

    f->ctx        = ic;
    f->ist_index  = nb_input_streams - ic->nb_streams;
    f->start_time = o->start_time;
    f->recording_time = o->recording_time;
    f->input_ts_offset = o->input_ts_offset;
    f->ts_offset  = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp);
    f->nb_streams = ic->nb_streams;
    f->rate_emu   = o->rate_emu;
    f->accurate_seek = o->accurate_seek;
    f->loop = o->loop;
    f->duration = 0;
    f->time_base = (AVRational) { 1, 1 };
#if HAVE_THREADS
    f->thread_queue_size = o->thread_queue_size > 0 ? o->thread_queue_size : 8;
#endif

    /* check if all codec options have been used */
    unused_opts = strip_specifiers(o->g->codec_opts);
    for (i = f->ist_index; i < nb_input_streams; i++) {
        e = NULL;
        while ((e = av_dict_get(input_streams[i]->decoder_opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
            av_dict_set(&unused_opts, e->key, NULL, 0);
        }
    }

    e = NULL;
    while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
        const AVClass* class = avcodec_get_class();
        const AVOption* option = av_opt_find(&class, e->key, NULL, 0, AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
        const AVClass* fclass = avformat_get_class();
        const AVOption* foption = av_opt_find(&fclass, e->key, NULL, 0, AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
        if (!option || foption) {
            continue;
        }
        if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) {
            av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for "
                   "input file #%d (%s) is not a decoding option.\n", e->key,
                   option->help ? option->help : "", nb_input_files - 1,
                   filename);
            exit_program(1);
        }
        av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for "
               "input file #%d (%s) has not been used for any stream. The most "
               "likely reason is either wrong type (e.g. a video option with "
               "no video streams) or that it is a private option of some decoder "
               "which was not actually used for any stream.\n", e->key,
               option->help ? option->help : "", nb_input_files - 1, filename);
    }
    av_dict_free(&unused_opts);

    for (i = 0; i < o->nb_dump_attachment; i++) {
        int j;
        for (j = 0; j < ic->nb_streams; j++) {
            AVStream* st = ic->streams[j];
            if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) {
                dump_attachment(st, o->dump_attachment[i].u.str);
            }
        }
    }

    input_stream_potentially_available = 1;

    return 0;
}

uint8_t* get_line(AVIOContext* s) {
    AVIOContext* line;
    uint8_t* buf;
    char c;

    if (avio_open_dyn_buf(&line) < 0) {
        av_log(NULL, AV_LOG_FATAL, "Could not alloc buffer for reading preset.\n");
        exit_program(1);
    }

    while ((c = avio_r8(s)) && c != '\n') {
        avio_w8(line, c);
    }
    avio_w8(line, 0);
    avio_close_dyn_buf(line, &buf);

    return buf;
}

int get_preset_file_2(const char* preset_name, const char* codec_name, AVIOContext** s) {
    int i, ret = -1;
    char filename[1000];
    const char* base[3] = { getenv("AVCONV_DATADIR"),
                            getenv("HOME"),
                            AVCONV_DATADIR,
                          };

    for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) {
        if (!base[i]) {
            continue;
        }
        if (codec_name) {
            snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], i != 1 ? "" : "/.avconv", codec_name, preset_name);
            ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL);
        }
        if (ret < 0) {
            snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], i != 1 ? "" : "/.avconv", preset_name);
            ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL);
        }
    }
    return ret;
}

int choose_encoder(OptionsContext* o, AVFormatContext* s, OutputStream* ost) {
    enum AVMediaType type = ost->st->codecpar->codec_type;
    char* codec_name = NULL;

    if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) {
        MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st);
        if (!codec_name) {
            ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url, NULL, ost->st->codecpar->codec_type);
            ost->enc = avcodec_find_encoder(ost->st->codecpar->codec_id);
            if (!ost->enc) {
                av_log(NULL, AV_LOG_FATAL, "Automatic encoder selection failed for "
                       "output stream #%d:%d. Default encoder for format %s (codec %s) is "
                       "probably disabled. Please choose an encoder manually.\n",
                       ost->file_index, ost->index, s->oformat->name,
                       avcodec_get_name(ost->st->codecpar->codec_id));
                return AVERROR_ENCODER_NOT_FOUND;
            }
        } else if (!strcmp(codec_name, "copy"))
            ost->stream_copy = 1;
        else {
            ost->enc = find_codec_or_die(codec_name, ost->st->codecpar->codec_type, 1);
            ost->st->codecpar->codec_id = ost->enc->id;
        }
        ost->encoding_needed = !ost->stream_copy;
    } else {
        /* no encoding supported for other media types */
        ost->stream_copy     = 1;
        ost->encoding_needed = 0;
    }
    return 0;
}

OutputStream* new_output_stream(OptionsContext* o, AVFormatContext* oc, enum AVMediaType type, int source_index) {
    OutputStream* ost;
    AVStream* st = avformat_new_stream(oc, NULL);
    int idx      = oc->nb_streams - 1, ret = 0;
    const char* bsfs = NULL, *time_base = NULL;
    char* next, *codec_tag = NULL;
    double qscale = -1;
    int i;

    if (!st) {
        av_log(NULL, AV_LOG_FATAL, "Could not alloc stream.\n");
        exit_program(1);
    }

    if (oc->nb_streams - 1 < o->nb_streamid_map) {
        st->id = o->streamid_map[oc->nb_streams - 1];
    }
    GROW_ARRAY(output_streams, nb_output_streams);
    if (!(ost = av_mallocz(sizeof(*ost)))) {
        exit_program(1);
    }
    output_streams[nb_output_streams - 1] = ost;

    ost->file_index = nb_output_files - 1;
    ost->index      = idx;
    ost->st         = st;
    ost->forced_kf_ref_pts = AV_NOPTS_VALUE;
    st->codecpar->codec_type = type;

    ret = choose_encoder(o, oc, ost);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error selecting an encoder for stream %d:%d\n", ost->file_index, ost->index);
        exit_program(1);
    }

    ost->enc_ctx = avcodec_alloc_context3(ost->enc);
    if (!ost->enc_ctx) {
        av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding context.\n");
        exit_program(1);
    }
    ost->enc_ctx->codec_type = type;

    ost->ref_par = avcodec_parameters_alloc();
    if (!ost->ref_par) {
        av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding parameters.\n");
        exit_program(1);
    }

    if (ost->enc) {
        AVIOContext* s = NULL;
        char* buf = NULL, *arg = NULL, *preset = NULL;

        ost->encoder_opts  = filter_codec_opts(o->g->codec_opts, ost->enc->id, oc, st, ost->enc);

        MATCH_PER_STREAM_OPT(presets, str, preset, oc, st);
        if (preset && (!(ret = get_preset_file_2(preset, ost->enc->name, &s)))) {
            do  {
                buf = get_line(s);
                if (!buf[0] || buf[0] == '#') {
                    av_free(buf);
                    continue;
                }
                if (!(arg = strchr(buf, '='))) {
                    av_log(NULL, AV_LOG_FATAL, "Invalid line found in the preset file.\n");
                    exit_program(1);
                }
                *arg++ = 0;
                av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE);
                av_free(buf);
            } while (!s->eof_reached);
            avio_closep(&s);
        }
        if (ret) {
            av_log(NULL, AV_LOG_FATAL, "Preset %s specified for stream %d:%d, but could not be opened.\n", preset, ost->file_index, ost->index);
            exit_program(1);
        }
    } else {
        ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL);
    }


    if (o->bitexact) {
        ost->enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT;
    }
    MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st);
    if (time_base) {
        AVRational q;
        if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || q.num <= 0 || q.den <= 0) {
            av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base);
            exit_program(1);
        }
        st->time_base = q;
    }

    MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st);
    if (time_base) {
        AVRational q;
        if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || q.den <= 0) {
            av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base);
            exit_program(1);
        }
        ost->enc_timebase = q;
    }

    ost->max_frames = INT64_MAX;
    MATCH_PER_STREAM_OPT(max_frames, i64, ost->max_frames, oc, st);
    for (i = 0; i < o->nb_max_frames; i++) {
        char* p = o->max_frames[i].specifier;
        if (!*p && type != AVMEDIA_TYPE_VIDEO) {
            av_log(NULL, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n");
            break;
        }
    }

    ost->copy_prior_start = -1;
    MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc, st);

    MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st);
    while (bsfs && *bsfs) {
        const AVBitStreamFilter* filter;
        char* bsf, *bsf_options_str, *bsf_name;

        bsf = av_get_token(&bsfs, ",");
        if (!bsf) {
            exit_program(1);
        }
        bsf_name = av_strtok(bsf, "=", &bsf_options_str);
        if (!bsf_name) {
            exit_program(1);
        }
        filter = av_bsf_get_by_name(bsf_name);
        if (!filter) {
            av_log(NULL, AV_LOG_FATAL, "Unknown bitstream filter %s\n", bsf_name);
            exit_program(1);
        }

        ost->bsf_ctx = av_realloc_array(ost->bsf_ctx, ost->nb_bitstream_filters + 1, sizeof(*ost->bsf_ctx));
        if (!ost->bsf_ctx) {
            exit_program(1);
        }
        ret = av_bsf_alloc(filter, &ost->bsf_ctx[ost->nb_bitstream_filters]);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error allocating a bitstream filter context\n");
            exit_program(1);
        }

        ost->nb_bitstream_filters++;

        if (bsf_options_str && filter->priv_class) {
            const AVOption* opt = av_opt_next(ost->bsf_ctx[ost->nb_bitstream_filters - 1]->priv_data, NULL);
            const char* shorthand[2] = {NULL};

            if (opt) {
                shorthand[0] = opt->name;
            }
            ret = av_opt_set_from_string(ost->bsf_ctx[ost->nb_bitstream_filters - 1]->priv_data, bsf_options_str, shorthand, "=", ":");
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error parsing options for bitstream filter %s\n", bsf_name);
                exit_program(1);
            }
        }
        av_freep(&bsf);

        if (*bsfs) {
            bsfs++;
        }
    }

    MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st);
    if (codec_tag) {
        uint32_t tag = strtol(codec_tag, &next, 0);
        if (*next) {
            tag = AV_RL32(codec_tag);
        }
        ost->st->codecpar->codec_tag = ost->enc_ctx->codec_tag = tag;
    }

    MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st);
    if (qscale >= 0) {
        ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE;
        ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale;
    }

    MATCH_PER_STREAM_OPT(disposition, str, ost->disposition, oc, st);
    ost->disposition = av_strdup(ost->disposition);

    ost->max_muxing_queue_size = 128;
    MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ost->max_muxing_queue_size, oc, st);
    ost->max_muxing_queue_size *= sizeof(AVPacket);

    if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
        ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
    av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0);

    av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0);
    if (ost->enc && av_get_exact_bits_per_sample(ost->enc->id) == 24) {
        av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0);
    }
    av_dict_copy(&ost->resample_opts, o->g->resample_opts, 0);

    ost->source_index = source_index;
    if (source_index >= 0) {
        ost->sync_ist = input_streams[source_index];
        input_streams[source_index]->discard = 0;
        input_streams[source_index]->st->discard = input_streams[source_index]->user_set_discard;
    }
    ost->last_mux_dts = AV_NOPTS_VALUE;

    ost->muxing_queue = av_fifo_alloc(8 * sizeof(AVPacket));
    if (!ost->muxing_queue) {
        exit_program(1);
    }
    return ost;
}

void parse_matrix_coeffs(uint16_t* dest, const char* str) {
    int i;
    const char* p = str;
    for (i = 0;; i++) {
        dest[i] = atoi(p);
        if (i == 63) {
            break;
        }
        p = strchr(p, ',');
        if (!p) {
            av_log(NULL, AV_LOG_FATAL, "Syntax error in matrix \"%s\" at coeff %d\n", str, i);
            exit_program(1);
        }
        p++;
    }
}

/* read file contents into a string */
static uint8_t* read_file(const char* filename) {
    AVIOContext* pb      = NULL;
    AVIOContext* dyn_buf = NULL;
    int ret = avio_open(&pb, filename, AVIO_FLAG_READ);
    uint8_t buf[1024], *str;

    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename);
        return NULL;
    }

    ret = avio_open_dyn_buf(&dyn_buf);
    if (ret < 0) {
        avio_closep(&pb);
        return NULL;
    }
    while ((ret = avio_read(pb, buf, sizeof(buf))) > 0) {
        avio_write(dyn_buf, buf, ret);
    }
    avio_w8(dyn_buf, 0);
    avio_closep(&pb);

    ret = avio_close_dyn_buf(dyn_buf, &str);
    if (ret < 0) {
        return NULL;
    }
    return str;
}

char* get_ost_filters(OptionsContext* o, AVFormatContext* oc, OutputStream* ost) {
    AVStream* st = ost->st;

    if (ost->filters_script && ost->filters) {
        av_log(NULL, AV_LOG_ERROR, "Both -filter and -filter_script set for output stream #%d:%d.\n", nb_output_files, st->index);
        exit_program(1);
    }

    if (ost->filters_script) {
        return read_file(ost->filters_script);
    } else if (ost->filters) {
        return av_strdup(ost->filters);
    }
    return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? "null" : "anull");
}

void check_streamcopy_filters(OptionsContext* o, AVFormatContext* oc, const OutputStream* ost, enum AVMediaType type) {
    if (ost->filters_script || ost->filters) {
        av_log(NULL, AV_LOG_ERROR,
               "%s '%s' was defined for %s output stream %d:%d but codec copy was selected.\n"
               "Filtering and streamcopy cannot be used together.\n",
               ost->filters ? "Filtergraph" : "Filtergraph script",
               ost->filters ? ost->filters : ost->filters_script,
               av_get_media_type_string(type), ost->file_index, ost->index);
        exit_program(1);
    }
}

OutputStream* new_video_stream(OptionsContext* o, AVFormatContext* oc, int source_index) {
    AVStream* st;
    OutputStream* ost;
    AVCodecContext* video_enc;
    char* frame_rate = NULL, *frame_aspect_ratio = NULL;

    ost = new_output_stream(o, oc, AVMEDIA_TYPE_VIDEO, source_index);
    st  = ost->st;
    video_enc = ost->enc_ctx;

    MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st);
    if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) {
        av_log(NULL, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate);
        exit_program(1);
    }
    if (frame_rate && video_sync_method == VSYNC_PASSTHROUGH) {
        av_log(NULL, AV_LOG_ERROR, "Using -vsync 0 and -r can produce invalid output files\n");
    }
    MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st);
    if (frame_aspect_ratio) {
        AVRational q;
        if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || q.num <= 0 || q.den <= 0) {
            av_log(NULL, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio);
            exit_program(1);
        }
        ost->frame_aspect_ratio = q;
    }

    MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st);
    MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st);
    if (o->nb_filters > 1) {
        av_log(NULL, AV_LOG_ERROR, "Only '-vf %s' read, ignoring remaining -vf options: Use ',' to separate filters\n", ost->filters);
    }
    if (!ost->stream_copy) {
        const char* p = NULL;
        char* frame_size = NULL;
        char* frame_pix_fmt = NULL;
        char* intra_matrix = NULL, *inter_matrix = NULL;
        char* chroma_intra_matrix = NULL;
        int do_pass = 0;
        int i;

        MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st);
        if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) {
            av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size);
            exit_program(1);
        }

        video_enc->bits_per_raw_sample = frame_bits_per_raw_sample;
        MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st);
        if (frame_pix_fmt && *frame_pix_fmt == '+') {
            ost->keep_pix_fmt = 1;
            if (!*++frame_pix_fmt) {
                frame_pix_fmt = NULL;
            }
        }
        if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) {
            av_log(NULL, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt);
            exit_program(1);
        }
        st->sample_aspect_ratio = video_enc->sample_aspect_ratio;

        if (intra_only) {
            video_enc->gop_size = 0;
        }
        MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st);
        if (intra_matrix) {
            if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) {
                av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n");
                exit_program(1);
            }
            parse_matrix_coeffs(video_enc->intra_matrix, intra_matrix);
        }
        MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st);
        if (chroma_intra_matrix) {
            uint16_t* p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64);
            if (!p) {
                av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n");
                exit_program(1);
            }
            video_enc->chroma_intra_matrix = p;
            parse_matrix_coeffs(p, chroma_intra_matrix);
        }
        MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st);
        if (inter_matrix) {
            if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) {
                av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for inter matrix.\n");
                exit_program(1);
            }
            parse_matrix_coeffs(video_enc->inter_matrix, inter_matrix);
        }

        MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st);
        for (i = 0; p; i++) {
            int start, end, q;
            int e = sscanf(p, "%d,%d,%d", &start, &end, &q);
            if (e != 3) {
                av_log(NULL, AV_LOG_FATAL, "error parsing rc_override\n");
                exit_program(1);
            }
            video_enc->rc_override = av_realloc_array(video_enc->rc_override, i + 1, sizeof(RcOverride));
            if (!video_enc->rc_override) {
                av_log(NULL, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n");
                exit_program(1);
            }
            video_enc->rc_override[i].start_frame = start;
            video_enc->rc_override[i].end_frame   = end;
            if (q > 0) {
                video_enc->rc_override[i].qscale         = q;
                video_enc->rc_override[i].quality_factor = 1.0;
            } else {
                video_enc->rc_override[i].qscale         = 0;
                video_enc->rc_override[i].quality_factor = -q / 100.0;
            }
            p = strchr(p, '/');
            if (p) p++;
        }
        video_enc->rc_override_count = i;

        if (do_psnr) {
            video_enc->flags |= AV_CODEC_FLAG_PSNR;
        }
        /* two pass mode */
        MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st);
        if (do_pass) {
            if (do_pass & 1) {
                video_enc->flags |= AV_CODEC_FLAG_PASS1;
                av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND);
            }
            if (do_pass & 2) {
                video_enc->flags |= AV_CODEC_FLAG_PASS2;
                av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND);
            }
        }

        MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st);
        if (ost->logfile_prefix && !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) {
            exit_program(1);
        }
        if (do_pass) {
            char logfilename[1024];
            FILE* f;
            snprintf(logfilename, sizeof(logfilename), "%s-%d.log", ost->logfile_prefix ? ost->logfile_prefix : DEFAULT_PASS_LOGFILENAME_PREFIX, i);
            if (!strcmp(ost->enc->name, "libx264")) {
                av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE);
            } else {
                if (video_enc->flags & AV_CODEC_FLAG_PASS2) {
                    char*  logbuffer = read_file(logfilename);
                    if (!logbuffer) {
                        av_log(NULL, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", logfilename);
                        exit_program(1);
                    }
                    video_enc->stats_in = logbuffer;
                }
                if (video_enc->flags & AV_CODEC_FLAG_PASS1) {
                    f = av_fopen_utf8(logfilename, "wb");
                    if (!f) {
                        av_log(NULL, AV_LOG_FATAL, "Cannot write log file '%s' for pass-1 encoding: %s\n", logfilename, strerror(errno));
                        exit_program(1);
                    }
                    ost->logfile = f;
                }
            }
        }

        MATCH_PER_STREAM_OPT(forced_key_frames, str, ost->forced_keyframes, oc, st);
        if (ost->forced_keyframes) {
            ost->forced_keyframes = av_strdup(ost->forced_keyframes);
        }
        MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st);

        ost->top_field_first = -1;
        MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st);

        ost->avfilter = get_ost_filters(o, oc, ost);
        if (!ost->avfilter) {
            exit_program(1);
        }
    } else {
        MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, ost->copy_initial_nonkeyframes, oc, st);
    }
    if (ost->stream_copy) {
        check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO);
    }
    return ost;
}

OutputStream* new_audio_stream(OptionsContext* o, AVFormatContext* oc, int source_index) {
    int n;
    AVStream* st;
    OutputStream* ost;
    AVCodecContext* audio_enc;

    ost = new_output_stream(o, oc, AVMEDIA_TYPE_AUDIO, source_index);
    st  = ost->st;

    audio_enc = ost->enc_ctx;
    audio_enc->codec_type = AVMEDIA_TYPE_AUDIO;

    MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st);
    MATCH_PER_STREAM_OPT(filters,        str, ost->filters,        oc, st);
    if (o->nb_filters > 1) {
        av_log(NULL, AV_LOG_ERROR, "Only '-af %s' read, ignoring remaining -af options: Use ',' to separate filters\n", ost->filters);
    }
    if (!ost->stream_copy) {
        char* sample_fmt = NULL;

        MATCH_PER_STREAM_OPT(audio_channels, i, audio_enc->channels, oc, st);
        MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st);

        if (sample_fmt && (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) {
            av_log(NULL, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt);
            exit_program(1);
        }

        MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st);

        MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st);
        ost->apad = av_strdup(ost->apad);

        ost->avfilter = get_ost_filters(o, oc, ost);
        if (!ost->avfilter) {
            exit_program(1);
        }
        /* check for channel mapping for this audio stream */
        for (n = 0; n < o->nb_audio_channel_maps; n++) {
            AudioChannelMap* map = &o->audio_channel_maps[n];
            if ((map->ofile_idx   == -1 || ost->file_index == map->ofile_idx) && (map->ostream_idx == -1 || ost->st->index  == map->ostream_idx)) {
                InputStream* ist;

                if (map->channel_idx == -1) {
                    ist = NULL;
                } else if (ost->source_index < 0) {
                    av_log(NULL, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", ost->file_index, ost->st->index);
                    continue;
                } else {
                    ist = input_streams[ost->source_index];
                }

                if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) {
                    if (av_reallocp_array(&ost->audio_channels_map, ost->audio_channels_mapped + 1, sizeof(*ost->audio_channels_map)) < 0) {
                        exit_program(1);
                    }
                    ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx;
                }
            }
        }
    }

    if (ost->stream_copy) {
        check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO);
    }
    return ost;
}

OutputStream* new_data_stream(OptionsContext* o, AVFormatContext* oc, int source_index) {
    OutputStream* ost;
    ost = new_output_stream(o, oc, AVMEDIA_TYPE_DATA, source_index);
    if (!ost->stream_copy) {
        av_log(NULL, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n");
        exit_program(1);
    }
    return ost;
}

OutputStream* new_unknown_stream(OptionsContext* o, AVFormatContext* oc, int source_index) {
    OutputStream* ost;
    ost = new_output_stream(o, oc, AVMEDIA_TYPE_UNKNOWN, source_index);
    if (!ost->stream_copy) {
        av_log(NULL, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n");
        exit_program(1);
    }
    return ost;
}

OutputStream* new_attachment_stream(OptionsContext* o, AVFormatContext* oc, int source_index) {
    OutputStream* ost = new_output_stream(o, oc, AVMEDIA_TYPE_ATTACHMENT, source_index);
    ost->stream_copy = 1;
    ost->finished    = 1;
    return ost;
}

OutputStream* new_subtitle_stream(OptionsContext* o, AVFormatContext* oc, int source_index) {
    AVStream* st;
    OutputStream* ost;
    AVCodecContext* subtitle_enc;

    ost = new_output_stream(o, oc, AVMEDIA_TYPE_SUBTITLE, source_index);
    st  = ost->st;

    subtitle_enc = ost->enc_ctx;
    subtitle_enc->codec_type = AVMEDIA_TYPE_SUBTITLE;

    MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, ost->copy_initial_nonkeyframes, oc, st);

    if (!ost->stream_copy) {
        char* frame_size = NULL;

        MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st);
        if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) {
            av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size);
            exit_program(1);
        }
    }

    return ost;
}

/* arg format is "output-stream-index:streamid-value". */
int opt_streamid(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    int idx;
    char* p;
    char idx_str[16];

    av_strlcpy(idx_str, arg, sizeof(idx_str));
    p = strchr(idx_str, ':');
    if (!p) {
        av_log(NULL, AV_LOG_FATAL, "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", arg, opt);
        exit_program(1);
    }
    *p++ = '\0';
    idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS - 1);
    o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx + 1);
    o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX);
    return 0;
}

int copy_chapters(InputFile* ifile, OutputFile* ofile, int copy_metadata) {
    AVFormatContext* is = ifile->ctx;
    AVFormatContext* os = ofile->ctx;
    AVChapter** tmp;
    int i;

    tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters));
    if (!tmp) {
        return AVERROR(ENOMEM);
    }
    os->chapters = tmp;

    for (i = 0; i < is->nb_chapters; i++) {
        AVChapter* in_ch = is->chapters[i], *out_ch;
        int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time;
        int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, AV_TIME_BASE_Q, in_ch->time_base);
        int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base);

        if (in_ch->end < ts_off) {
            continue;
        }
        if (rt != INT64_MAX && in_ch->start > rt + ts_off) {
            break;
        }
        out_ch = av_mallocz(sizeof(AVChapter));
        if (!out_ch) {
            return AVERROR(ENOMEM);
        }
        out_ch->id        = in_ch->id;
        out_ch->time_base = in_ch->time_base;
        out_ch->start     = FFMAX(0,  in_ch->start - ts_off);
        out_ch->end       = FFMIN(rt, in_ch->end   - ts_off);

        if (copy_metadata) {
            av_dict_copy(&out_ch->metadata, in_ch->metadata, 0);
        }
        os->chapters[os->nb_chapters++] = out_ch;
    }
    return 0;
}

void init_output_filter(OutputFilter* ofilter, OptionsContext* o, AVFormatContext* oc) {
    OutputStream* ost = NULL;
    switch (ofilter->type) {
        case AVMEDIA_TYPE_VIDEO:
            ost = new_video_stream(o, oc, -1);
            break;
        case AVMEDIA_TYPE_AUDIO:
            ost = new_audio_stream(o, oc, -1);
            break;
        default:
            av_log(NULL, AV_LOG_FATAL, "Only video and audio filters are supported "  "currently.\n");
            exit_program(1);
    }

    ost->source_index = -1;
    ost->filter       = ofilter;

    ofilter->ost      = ost;
    ofilter->format   = -1;

    if (ost->stream_copy) {
        av_log(NULL, AV_LOG_ERROR, "Streamcopy requested for output stream %d:%d, "
               "which is fed from a complex filtergraph. Filtering and streamcopy cannot be used together.\n", ost->file_index, ost->index);
        exit_program(1);
    }

    if (ost->avfilter && (ost->filters || ost->filters_script)) {
        const char* opt = ost->filters ? "-vf/-af/-filter" : "-filter_script";
        av_log(NULL, AV_LOG_ERROR,
               "%s '%s' was specified through the %s option for output stream %d:%d, which is fed from a complex filtergraph.\n"
               "%s and -filter_complex cannot be used together for the same stream.\n",
               ost->filters ? "Filtergraph" : "Filtergraph script",
               ost->filters ? ost->filters : ost->filters_script,
               opt, ost->file_index, ost->index, opt);
        exit_program(1);
    }

    avfilter_inout_free(&ofilter->out_tmp);
}

int init_complex_filters(void) {
    int i, ret = 0;

    for (i = 0; i < nb_filtergraphs; i++) {
        ret = init_complex_filtergraph(filtergraphs[i]);
        if (ret < 0) {
            return ret;
        }
    }
    return 0;
}

int open_output_file(OptionsContext* o, const char* filename) {
    AVFormatContext* oc;
    int i, j, err;
    OutputFile* of;
    OutputStream* ost;
    InputStream*  ist;
    AVDictionary* unused_opts = NULL;
    AVDictionaryEntry* e = NULL;
    int format_flags = 0;

    if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) {
        o->stop_time = INT64_MAX;
        av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n");
    }

    if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) {
        int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time;
        if (o->stop_time <= start_time) {
            av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n");
            exit_program(1);
        } else {
            o->recording_time = o->stop_time - start_time;
        }
    }

    GROW_ARRAY(output_files, nb_output_files);
    of = av_mallocz(sizeof(*of));
    if (!of) {
        exit_program(1);
    }
    output_files[nb_output_files - 1] = of;

    of->ost_index      = nb_output_streams;
    of->recording_time = o->recording_time;
    of->start_time     = o->start_time;
    of->limit_filesize = o->limit_filesize;
    of->shortest       = o->shortest;
    av_dict_copy(&of->opts, o->g->format_opts, 0);

    if (!strcmp(filename, "-")) {
        filename = "pipe:";
    }
    err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);
    if (!oc) {
        print_error(filename, err);
        exit_program(1);
    }

    of->ctx = oc;
    if (o->recording_time != INT64_MAX) {
        oc->duration = o->recording_time;
    }
    oc->interrupt_callback = int_cb;

    e = av_dict_get(o->g->format_opts, "fflags", NULL, 0);
    if (e) {
        const AVOption* o = av_opt_find(oc, "fflags", NULL, 0, 0);
        av_opt_eval_flags(oc, o, e->value, &format_flags);
    }
    if (o->bitexact) {
        format_flags |= AVFMT_FLAG_BITEXACT;
        oc->flags    |= AVFMT_FLAG_BITEXACT;
    }

    /* create streams for all unlabeled output pads */
    for (i = 0; i < nb_filtergraphs; i++) {
        FilterGraph* fg = filtergraphs[i];
        for (j = 0; j < fg->nb_outputs; j++) {
            OutputFilter* ofilter = fg->outputs[j];

            if (!ofilter->out_tmp || ofilter->out_tmp->name) {
                continue;
            }
            switch (ofilter->type) {
                case AVMEDIA_TYPE_VIDEO:    o->video_disable    = 1; break;
                case AVMEDIA_TYPE_AUDIO:    o->audio_disable    = 1; break;
                case AVMEDIA_TYPE_SUBTITLE: o->subtitle_disable = 1; break;
            }
            init_output_filter(ofilter, o, oc);
        }
    }

    if (!o->nb_stream_maps) {
        char* subtitle_codec_name = NULL;
        /* pick the "best" stream of each type */

        /* video: highest resolution */
        if (!o->video_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_VIDEO) != AV_CODEC_ID_NONE) {
            int area = 0, idx = -1;
            int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0);
            for (i = 0; i < nb_input_streams; i++) {
                int new_area;
                ist = input_streams[i];
                new_area = ist->st->codecpar->width * ist->st->codecpar->height + 100000000 * !!ist->st->codec_info_nb_frames
                           + 5000000 * !!(ist->st->disposition & AV_DISPOSITION_DEFAULT);
                if (ist->user_set_discard == AVDISCARD_ALL)
                    continue;
                if ((qcr != MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC))
                    new_area = 1;
                if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
                        new_area > area) {
                    if ((qcr == MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC))
                        continue;
                    area = new_area;
                    idx = i;
                }
            }
            if (idx >= 0) {
                new_video_stream(o, oc, idx);
            }
        }

        /* audio: most channels */
        if (!o->audio_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_AUDIO) != AV_CODEC_ID_NONE) {
            int best_score = 0, idx = -1;
            for (i = 0; i < nb_input_streams; i++) {
                int score;
                ist = input_streams[i];
                score = ist->st->codecpar->channels + 100000000 * !!ist->st->codec_info_nb_frames
                        + 5000000 * !!(ist->st->disposition & AV_DISPOSITION_DEFAULT);
                if (ist->user_set_discard == AVDISCARD_ALL)
                    continue;
                if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
                        score > best_score) {
                    best_score = score;
                    idx = i;
                }
            }
            if (idx >= 0)
                new_audio_stream(o, oc, idx);
        }

        /* subtitles: pick first */
        MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s");
        if (!o->subtitle_disable && (avcodec_find_encoder(oc->oformat->subtitle_codec) || subtitle_codec_name)) {
            for (i = 0; i < nb_input_streams; i++)
                if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
                    AVCodecDescriptor const* input_descriptor =
                        avcodec_descriptor_get(input_streams[i]->st->codecpar->codec_id);
                    AVCodecDescriptor const* output_descriptor = NULL;
                    AVCodec const* output_codec =
                        avcodec_find_encoder(oc->oformat->subtitle_codec);
                    int input_props = 0, output_props = 0;
                    if (input_streams[i]->user_set_discard == AVDISCARD_ALL)
                        continue;
                    if (output_codec)
                        output_descriptor = avcodec_descriptor_get(output_codec->id);
                    if (input_descriptor)
                        input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
                    if (output_descriptor)
                        output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
                    if (subtitle_codec_name ||
                            input_props & output_props ||
                            // Map dvb teletext which has neither property to any output subtitle encoder
                            input_descriptor && output_descriptor &&
                            (!input_descriptor->props ||
                             !output_descriptor->props)) {
                        new_subtitle_stream(o, oc, i);
                        break;
                    }
                }
        }
        /* Data only if codec id match */
        if (!o->data_disable) {
            enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_DATA);
            for (i = 0; codec_id != AV_CODEC_ID_NONE && i < nb_input_streams; i++) {
                if (input_streams[i]->user_set_discard == AVDISCARD_ALL)
                    continue;
                if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_DATA
                        && input_streams[i]->st->codecpar->codec_id == codec_id)
                    new_data_stream(o, oc, i);
            }
        }
    } else {
        for (i = 0; i < o->nb_stream_maps; i++) {
            StreamMap* map = &o->stream_maps[i];

            if (map->disabled)
                continue;

            if (map->linklabel) {
                FilterGraph* fg;
                OutputFilter* ofilter = NULL;
                int j, k;

                for (j = 0; j < nb_filtergraphs; j++) {
                    fg = filtergraphs[j];
                    for (k = 0; k < fg->nb_outputs; k++) {
                        AVFilterInOut* out = fg->outputs[k]->out_tmp;
                        if (out && !strcmp(out->name, map->linklabel)) {
                            ofilter = fg->outputs[k];
                            goto loop_end;
                        }
                    }
                }
loop_end:
                if (!ofilter) {
                    av_log(NULL, AV_LOG_FATAL, "Output with label '%s' does not exist "
                           "in any defined filter graph, or was already used elsewhere.\n", map->linklabel);
                    exit_program(1);
                }
                init_output_filter(ofilter, o, oc);
            } else {
                int src_idx = input_files[map->file_index]->ist_index + map->stream_index;

                ist = input_streams[input_files[map->file_index]->ist_index + map->stream_index];
                if (ist->user_set_discard == AVDISCARD_ALL) {
                    av_log(NULL, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n",
                           map->file_index, map->stream_index);
                    exit_program(1);
                }
                if (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)
                    continue;
                if (o->   audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
                    continue;
                if (o->   video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
                    continue;
                if (o->    data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)
                    continue;

                ost = NULL;
                switch (ist->st->codecpar->codec_type) {
                    case AVMEDIA_TYPE_VIDEO:      ost = new_video_stream(o, oc, src_idx); break;
                    case AVMEDIA_TYPE_AUDIO:      ost = new_audio_stream(o, oc, src_idx); break;
                    case AVMEDIA_TYPE_SUBTITLE:   ost = new_subtitle_stream(o, oc, src_idx); break;
                    case AVMEDIA_TYPE_DATA:       ost = new_data_stream(o, oc, src_idx); break;
                    case AVMEDIA_TYPE_ATTACHMENT: ost = new_attachment_stream(o, oc, src_idx); break;
                    case AVMEDIA_TYPE_UNKNOWN:
                        if (copy_unknown_streams) {
                            ost = new_unknown_stream(o, oc, src_idx);
                            break;
                        }
                    default:
                        av_log(NULL, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL,
                               "Cannot map stream #%d:%d - unsupported type.\n",
                               map->file_index, map->stream_index);
                        if (!ignore_unknown_streams) {
                            av_log(NULL, AV_LOG_FATAL,
                                   "If you want unsupported types ignored instead "
                                   "of failing, please use the -ignore_unknown option\n"
                                   "If you want them copied, please use -copy_unknown\n");
                            exit_program(1);
                        }
                }
                if (ost)
                    ost->sync_ist = input_streams[  input_files[map->sync_file_index]->ist_index
                                                    + map->sync_stream_index];
            }
        }
    }

    /* handle attached files */
    for (i = 0; i < o->nb_attachments; i++) {
        AVIOContext* pb;
        uint8_t* attachment;
        const char* p;
        int64_t len;

        if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) {
            av_log(NULL, AV_LOG_FATAL, "Could not open attachment file %s.\n",
                   o->attachments[i]);
            exit_program(1);
        }
        if ((len = avio_size(pb)) <= 0) {
            av_log(NULL, AV_LOG_FATAL, "Could not get size of the attachment %s.\n",
                   o->attachments[i]);
            exit_program(1);
        }
        if (!(attachment = av_malloc(len))) {
            av_log(NULL, AV_LOG_FATAL, "Attachment %s too large to fit into memory.\n",
                   o->attachments[i]);
            exit_program(1);
        }
        avio_read(pb, attachment, len);

        ost = new_attachment_stream(o, oc, -1);
        ost->stream_copy               = 0;
        ost->attachment_filename       = o->attachments[i];
        ost->st->codecpar->extradata      = attachment;
        ost->st->codecpar->extradata_size = len;

        p = strrchr(o->attachments[i], '/');
        av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE);
        avio_closep(&pb);
    }

#if FF_API_LAVF_AVCTX
    for (i = nb_output_streams - oc->nb_streams; i < nb_output_streams; i++) { //for all streams of this output file
        AVDictionaryEntry* e;
        ost = output_streams[i];

        if ((ost->stream_copy || ost->attachment_filename)
                && (e = av_dict_get(o->g->codec_opts, "flags", NULL, AV_DICT_IGNORE_SUFFIX))
                && (!e->key[5] || check_stream_specifier(oc, ost->st, e->key + 6)))
            if (av_opt_set(ost->st->codec, "flags", e->value, 0) < 0)
                exit_program(1);
    }
#endif

    if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) {
        av_dump_format(oc, nb_output_files - 1, oc->url, 1);
        av_log(NULL, AV_LOG_ERROR, "Output file #%d does not contain any stream\n", nb_output_files - 1);
        exit_program(1);
    }

    /* check if all codec options have been used */
    unused_opts = strip_specifiers(o->g->codec_opts);
    for (i = of->ost_index; i < nb_output_streams; i++) {
        e = NULL;
        while ((e = av_dict_get(output_streams[i]->encoder_opts, "", e,
                                AV_DICT_IGNORE_SUFFIX)))
            av_dict_set(&unused_opts, e->key, NULL, 0);
    }

    e = NULL;
    while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
        const AVClass* class = avcodec_get_class();
        const AVOption* option = av_opt_find(&class, e->key, NULL, 0,
                                             AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
        const AVClass* fclass = avformat_get_class();
        const AVOption* foption = av_opt_find(&fclass, e->key, NULL, 0,
                                              AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
        if (!option || foption)
            continue;


        if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) {
            av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for "
                   "output file #%d (%s) is not an encoding option.\n", e->key,
                   option->help ? option->help : "", nb_output_files - 1,
                   filename);
            exit_program(1);
        }

        // gop_timecode is injected by generic code but not always used
        if (!strcmp(e->key, "gop_timecode"))
            continue;

        av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for "
               "output file #%d (%s) has not been used for any stream. The most "
               "likely reason is either wrong type (e.g. a video option with "
               "no video streams) or that it is a private option of some encoder "
               "which was not actually used for any stream.\n", e->key,
               option->help ? option->help : "", nb_output_files - 1, filename);
    }
    av_dict_free(&unused_opts);

    /* set the decoding_needed flags and create simple filtergraphs */
    for (i = of->ost_index; i < nb_output_streams; i++) {
        OutputStream* ost = output_streams[i];

        if (ost->encoding_needed && ost->source_index >= 0) {
            InputStream* ist = input_streams[ost->source_index];
            ist->decoding_needed |= DECODING_FOR_OST;

            if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
                    ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
                err = init_simple_filtergraph(ist, ost);
                if (err < 0) {
                    av_log(NULL, AV_LOG_ERROR,
                           "Error initializing a simple filtergraph between streams "
                           "%d:%d->%d:%d\n", ist->file_index, ost->source_index,
                           nb_output_files - 1, ost->st->index);
                    exit_program(1);
                }
            }
        }

        /* set the filter output constraints */
        if (ost->filter) {
            OutputFilter* f = ost->filter;
            int count;
            switch (ost->enc_ctx->codec_type) {
                case AVMEDIA_TYPE_VIDEO:
                    f->frame_rate = ost->frame_rate;
                    f->width      = ost->enc_ctx->width;
                    f->height     = ost->enc_ctx->height;
                    if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) {
                        f->format = ost->enc_ctx->pix_fmt;
                    } else if (ost->enc->pix_fmts) {
                        count = 0;
                        while (ost->enc->pix_fmts[count] != AV_PIX_FMT_NONE)
                            count++;
                        f->formats = av_mallocz_array(count + 1, sizeof(*f->formats));
                        if (!f->formats)
                            exit_program(1);
                        memcpy(f->formats, ost->enc->pix_fmts, (count + 1) * sizeof(*f->formats));
                    }
                    break;
                case AVMEDIA_TYPE_AUDIO:
                    if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) {
                        f->format = ost->enc_ctx->sample_fmt;
                    } else if (ost->enc->sample_fmts) {
                        count = 0;
                        while (ost->enc->sample_fmts[count] != AV_SAMPLE_FMT_NONE)
                            count++;
                        f->formats = av_mallocz_array(count + 1, sizeof(*f->formats));
                        if (!f->formats)
                            exit_program(1);
                        memcpy(f->formats, ost->enc->sample_fmts, (count + 1) * sizeof(*f->formats));
                    }
                    if (ost->enc_ctx->sample_rate) {
                        f->sample_rate = ost->enc_ctx->sample_rate;
                    } else if (ost->enc->supported_samplerates) {
                        count = 0;
                        while (ost->enc->supported_samplerates[count])
                            count++;
                        f->sample_rates = av_mallocz_array(count + 1, sizeof(*f->sample_rates));
                        if (!f->sample_rates)
                            exit_program(1);
                        memcpy(f->sample_rates, ost->enc->supported_samplerates,
                               (count + 1) * sizeof(*f->sample_rates));
                    }
                    if (ost->enc_ctx->channels) {
                        f->channel_layout = av_get_default_channel_layout(ost->enc_ctx->channels);
                    } else if (ost->enc->channel_layouts) {
                        count = 0;
                        while (ost->enc->channel_layouts[count])
                            count++;
                        f->channel_layouts = av_mallocz_array(count + 1, sizeof(*f->channel_layouts));
                        if (!f->channel_layouts)
                            exit_program(1);
                        memcpy(f->channel_layouts, ost->enc->channel_layouts,
                               (count + 1) * sizeof(*f->channel_layouts));
                    }
                    break;
            }
        }
    }

    /* check filename in case of an image number is expected */
    if (oc->oformat->flags & AVFMT_NEEDNUMBER) {
        if (!av_filename_number_test(oc->url)) {
            print_error(oc->url, AVERROR(EINVAL));
            exit_program(1);
        }
    }

    if (!(oc->oformat->flags & AVFMT_NOSTREAMS) && !input_stream_potentially_available) {
        av_log(NULL, AV_LOG_ERROR,
               "No input streams but output needs an input stream\n");
        exit_program(1);
    }

    if (!(oc->oformat->flags & AVFMT_NOFILE)) {
        /* open the file */
        if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,
                              &oc->interrupt_callback,
                              &of->opts)) < 0) {
            print_error(filename, err);
            exit_program(1);
        }
    }

    if (o->mux_preload) {
        av_dict_set_int(&of->opts, "preload", o->mux_preload * AV_TIME_BASE, 0);
    }
    oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE);

    /* copy metadata */
    for (i = 0; i < o->nb_metadata_map; i++) {
        char* p;
        int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0);

        if (in_file_index >= nb_input_files) {
            av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d while processing metadata maps\n", in_file_index);
            exit_program(1);
        }
        copy_metadata(o->metadata_map[i].specifier, *p ? p + 1 : p, oc,
                      in_file_index >= 0 ?
                      input_files[in_file_index]->ctx : NULL, o);
    }

    /* copy chapters */
    if (o->chapters_input_file >= nb_input_files) {
        if (o->chapters_input_file == INT_MAX) {
            /* copy chapters from the first input file that has them*/
            o->chapters_input_file = -1;
            for (i = 0; i < nb_input_files; i++)
                if (input_files[i]->ctx->nb_chapters) {
                    o->chapters_input_file = i;
                    break;
                }
        } else {
            av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n",
                   o->chapters_input_file);
            exit_program(1);
        }
    }
    if (o->chapters_input_file >= 0)
        copy_chapters(input_files[o->chapters_input_file], of,
                      !o->metadata_chapters_manual);

    /* copy global metadata by default */
    if (!o->metadata_global_manual && nb_input_files) {
        av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata,
                     AV_DICT_DONT_OVERWRITE);
        if (o->recording_time != INT64_MAX)
            av_dict_set(&oc->metadata, "duration", NULL, 0);
        av_dict_set(&oc->metadata, "creation_time", NULL, 0);
    }
    if (!o->metadata_streams_manual)
        for (i = of->ost_index; i < nb_output_streams; i++) {
            InputStream* ist;
            if (output_streams[i]->source_index < 0)         /* this is true e.g. for attached files */
                continue;
            ist = input_streams[output_streams[i]->source_index];
            av_dict_copy(&output_streams[i]->st->metadata, ist->st->metadata, AV_DICT_DONT_OVERWRITE);
            if (!output_streams[i]->stream_copy) {
                av_dict_set(&output_streams[i]->st->metadata, "encoder", NULL, 0);
            }
        }

    /* process manually set programs */
    for (i = 0; i < o->nb_program; i++) {
        const char* p = o->program[i].u.str;
        int progid = i + 1;
        AVProgram* program;

        while (*p) {
            const char* p2 = av_get_token(&p, ":");
            const char* to_dealloc = p2;
            char* key;
            if (!p2)
                break;

            if (*p) p++;

            key = av_get_token(&p2, "=");
            if (!key || !*p2) {
                av_freep(&to_dealloc);
                av_freep(&key);
                break;
            }
            p2++;

            if (!strcmp(key, "program_num"))
                progid = strtol(p2, NULL, 0);
            av_freep(&to_dealloc);
            av_freep(&key);
        }

        program = av_new_program(oc, progid);

        p = o->program[i].u.str;
        while (*p) {
            const char* p2 = av_get_token(&p, ":");
            const char* to_dealloc = p2;
            char* key;
            if (!p2)
                break;
            if (*p) p++;

            key = av_get_token(&p2, "=");
            if (!key) {
                av_log(NULL, AV_LOG_FATAL,
                       "No '=' character in program string %s.\n",
                       p2);
                exit_program(1);
            }
            if (!*p2)
                exit_program(1);
            p2++;

            if (!strcmp(key, "title")) {
                av_dict_set(&program->metadata, "title", p2, 0);
            } else if (!strcmp(key, "program_num")) {
            } else if (!strcmp(key, "st")) {
                int st_num = strtol(p2, NULL, 0);
                av_program_add_stream_index(oc, progid, st_num);
            } else {
                av_log(NULL, AV_LOG_FATAL, "Unknown program key %s.\n", key);
                exit_program(1);
            }
            av_freep(&to_dealloc);
            av_freep(&key);
        }
    }

    /* process manually set metadata */
    for (i = 0; i < o->nb_metadata; i++) {
        AVDictionary** m = NULL;
        char type, *val = NULL;
        const char* stream_spec = NULL;
        int index = 0, j, ret = 0;

        val = strchr(o->metadata[i].u.str, '=');
        if (!val) {
            av_log(NULL, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", o->metadata[i].u.str);
            exit_program(1);
        }
        *val++ = 0;

        parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec);
        if (type == 's') {
            for (j = 0; j < oc->nb_streams; j++) {
                ost = output_streams[nb_output_streams - oc->nb_streams + j];
                if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) {
                    if (!strcmp(o->metadata[i].u.str, "rotate")) {
                        char* tail;
                        double theta = av_strtod(val, &tail);
                        if (!*tail) {
                            ost->rotate_overridden = 1;
                            ost->rotate_override_value = theta;
                        }
                    } else {
                        av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0);
                    }
                } else if (ret < 0)
                    exit_program(1);
            }
        } else {
            switch (type) {
                case 'g':
                    m = &oc->metadata;
                    break;
                case 'c':
                    if (index < 0 || index >= oc->nb_chapters) {
                        av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index);
                        exit_program(1);
                    }
                    m = &oc->chapters[index]->metadata;
                    break;
                case 'p':
                    if (index < 0 || index >= oc->nb_programs) {
                        av_log(NULL, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index);
                        exit_program(1);
                    }
                    m = &oc->programs[index]->metadata;
                    break;
                default:
                    av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier);
                    exit_program(1);
            }
            av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0);
        }
    }

    return 0;
}

int opt_target(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN;
    static const char* const frame_rates[] = { "25", "30000/1001", "24000/1001" };

    if (!strncmp(arg, "pal-", 4)) {
        norm = PAL;
        arg += 4;
    } else if (!strncmp(arg, "ntsc-", 5)) {
        norm = NTSC;
        arg += 5;
    } else if (!strncmp(arg, "film-", 5)) {
        norm = FILM;
        arg += 5;
    } else {
        /* Try to determine PAL/NTSC by peeking in the input files */
        if (nb_input_files) {
            int i, j, fr;
            for (j = 0; j < nb_input_files; j++) {
                for (i = 0; i < input_files[j]->nb_streams; i++) {
                    AVStream* st = input_files[j]->ctx->streams[i];
                    if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
                        continue;
                    fr = st->time_base.den * 1000 / st->time_base.num;
                    if (fr == 25000) {
                        norm = PAL;
                        break;
                    } else if ((fr == 29970) || (fr == 23976)) {
                        norm = NTSC;
                        break;
                    }
                }
                if (norm != UNKNOWN)
                    break;
            }
        }
        if (norm != UNKNOWN)
            av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC");
    }

    if (norm == UNKNOWN) {
        av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n");
        av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n");
        av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n");
        exit_program(1);
    }

    if (!strcmp(arg, "vcd")) {
        opt_video_codec(o, "c:v", "mpeg1video");
        opt_audio_codec(o, "c:a", "mp2");
        parse_option(o, "f", "vcd", options);

        parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options);
        parse_option(o, "r", frame_rates[norm], options);
        opt_default(NULL, "g", norm == PAL ? "15" : "18");

        opt_default(NULL, "b:v", "1150000");
        opt_default(NULL, "maxrate:v", "1150000");
        opt_default(NULL, "minrate:v", "1150000");
        opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8;

        opt_default(NULL, "b:a", "224000");
        parse_option(o, "ar", "44100", options);
        parse_option(o, "ac", "2", options);

        opt_default(NULL, "packetsize", "2324");
        opt_default(NULL, "muxrate", "1411200"); // 2352 * 75 * 8;

        /* We have to offset the PTS, so that it is consistent with the SCR.
           SCR starts at 36000, but the first two packs contain only padding
           and the first pack from the other stream, respectively, may also have
           been written before.
           So the real data starts at SCR 36000+3*1200. */
        o->mux_preload = (36000 + 3 * 1200) / 90000.0; // 0.44
    } else if (!strcmp(arg, "svcd")) {

        opt_video_codec(o, "c:v", "mpeg2video");
        opt_audio_codec(o, "c:a", "mp2");
        parse_option(o, "f", "svcd", options);

        parse_option(o, "s", norm == PAL ? "480x576" : "480x480", options);
        parse_option(o, "r", frame_rates[norm], options);
        parse_option(o, "pix_fmt", "yuv420p", options);
        opt_default(NULL, "g", norm == PAL ? "15" : "18");

        opt_default(NULL, "b:v", "2040000");
        opt_default(NULL, "maxrate:v", "2516000");
        opt_default(NULL, "minrate:v", "0"); // 1145000;
        opt_default(NULL, "bufsize:v", "1835008"); // 224*1024*8;
        opt_default(NULL, "scan_offset", "1");

        opt_default(NULL, "b:a", "224000");
        parse_option(o, "ar", "44100", options);

        opt_default(NULL, "packetsize", "2324");

    } else if (!strcmp(arg, "dvd")) {

        opt_video_codec(o, "c:v", "mpeg2video");
        opt_audio_codec(o, "c:a", "ac3");
        parse_option(o, "f", "dvd", options);

        parse_option(o, "s", norm == PAL ? "720x576" : "720x480", options);
        parse_option(o, "r", frame_rates[norm], options);
        parse_option(o, "pix_fmt", "yuv420p", options);
        opt_default(NULL, "g", norm == PAL ? "15" : "18");

        opt_default(NULL, "b:v", "6000000");
        opt_default(NULL, "maxrate:v", "9000000");
        opt_default(NULL, "minrate:v", "0"); // 1500000;
        opt_default(NULL, "bufsize:v", "1835008"); // 224*1024*8;

        opt_default(NULL, "packetsize", "2048");  // from www.mpucoder.com: DVD sectors contain 2048 bytes of data, this is also the size of one pack.
        opt_default(NULL, "muxrate", "10080000"); // from mplex project: data_rate = 1260000. mux_rate = data_rate * 8

        opt_default(NULL, "b:a", "448000");
        parse_option(o, "ar", "48000", options);

    } else if (!strncmp(arg, "dv", 2)) {

        parse_option(o, "f", "dv", options);

        parse_option(o, "s", norm == PAL ? "720x576" : "720x480", options);
        parse_option(o, "pix_fmt", !strncmp(arg, "dv50", 4) ? "yuv422p" :
                     norm == PAL ? "yuv420p" : "yuv411p", options);
        parse_option(o, "r", frame_rates[norm], options);

        parse_option(o, "ar", "48000", options);
        parse_option(o, "ac", "2", options);

    } else {
        av_log(NULL, AV_LOG_ERROR, "Unknown target: %s\n", arg);
        return AVERROR(EINVAL);
    }

    av_dict_copy(&o->g->codec_opts,  codec_opts, AV_DICT_DONT_OVERWRITE);
    av_dict_copy(&o->g->format_opts, format_opts, AV_DICT_DONT_OVERWRITE);

    return 0;
}

int opt_video_frames(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "frames:v", arg, options);
}

int opt_audio_frames(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "frames:a", arg, options);
}

int opt_data_frames(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "frames:d", arg, options);
}

int opt_default_new(OptionsContext* o, const char* opt, const char* arg) {
    int ret;
    AVDictionary* cbak = codec_opts;
    AVDictionary* fbak = format_opts;
    codec_opts = NULL;
    format_opts = NULL;

    ret = opt_default(NULL, opt, arg);

    av_dict_copy(&o->g->codec_opts, codec_opts, 0);
    av_dict_copy(&o->g->format_opts, format_opts, 0);
    av_dict_free(&codec_opts);
    av_dict_free(&format_opts);
    codec_opts = cbak;
    format_opts = fbak;

    return ret;
}

int opt_preset(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    FILE* f = NULL;
    char filename[1000], line[1000], tmp_line[1000];
    const char* codec_name = NULL;

    tmp_line[0] = *opt;
    tmp_line[1] = 0;
    MATCH_PER_TYPE_OPT(codec_names, str, codec_name, NULL, tmp_line);

    if (!(f = get_preset_file(filename, sizeof(filename), arg, *opt == 'f', codec_name))) {
        if (!strncmp(arg, "libx264-lossless", strlen("libx264-lossless"))) {
            av_log(NULL, AV_LOG_FATAL, "Please use -preset <speed> -qp 0\n");
        } else
            av_log(NULL, AV_LOG_FATAL, "File for preset '%s' not found\n", arg);
        exit_program(1);
    }

    while (fgets(line, sizeof(line), f)) {
        char* key = tmp_line, *value, *endptr;

        if (strcspn(line, "#\n\r") == 0)
            continue;
        av_strlcpy(tmp_line, line, sizeof(tmp_line));
        if (!av_strtok(key,   "=",    &value) ||
                !av_strtok(value, "\r\n", &endptr)) {
            av_log(NULL, AV_LOG_FATAL, "%s: Invalid syntax: '%s'\n", filename, line);
            exit_program(1);
        }
        av_log(NULL, AV_LOG_DEBUG, "ffpreset[%s]: set '%s' = '%s'\n", filename, key, value);

        if (!strcmp(key, "acodec")) opt_audio_codec(o, key, value);
        else if (!strcmp(key, "vcodec")) opt_video_codec(o, key, value);
        else if (!strcmp(key, "scodec")) opt_subtitle_codec(o, key, value);
        else if (!strcmp(key, "dcodec")) opt_data_codec(o, key, value);
        else if (opt_default_new(o, key, value) < 0) {
            av_log(NULL, AV_LOG_FATAL, "%s: Invalid option or argument: '%s', parsed as '%s' = '%s'\n",
                   filename, line, key, value);
            exit_program(1);
        }
    }

    fclose(f);

    return 0;
}

int opt_old2new(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    char* s = av_asprintf("%s:%c", opt + 1, *opt);
    int ret = parse_option(o, s, arg, options);
    av_free(s);
    return ret;
}

int opt_bitrate(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    if (!strcmp(opt, "ab")) {
        av_dict_set(&o->g->codec_opts, "b:a", arg, 0);
        return 0;
    } else if (!strcmp(opt, "b")) {
        av_log(NULL, AV_LOG_WARNING, "Please use -b:a or -b:v, -b is ambiguous\n");
        av_dict_set(&o->g->codec_opts, "b:v", arg, 0);
        return 0;
    }
    av_dict_set(&o->g->codec_opts, opt, arg, 0);
    return 0;
}

int opt_qscale(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    char* s;
    int ret;
    if (!strcmp(opt, "qscale")) {
        av_log(NULL, AV_LOG_WARNING, "Please use -q:a or -q:v, -qscale is ambiguous\n");
        return parse_option(o, "q:v", arg, options);
    }
    s = av_asprintf("q%s", opt + 6);
    ret = parse_option(o, s, arg, options);
    av_free(s);
    return ret;
}

int opt_profile(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    if (!strcmp(opt, "profile")) {
        av_log(NULL, AV_LOG_WARNING, "Please use -profile:a or -profile:v, -profile is ambiguous\n");
        av_dict_set(&o->g->codec_opts, "profile:v", arg, 0);
        return 0;
    }
    av_dict_set(&o->g->codec_opts, opt, arg, 0);
    return 0;
}

int opt_video_filters(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "filter:v", arg, options);
}

int opt_audio_filters(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "filter:a", arg, options);
}

int opt_vsync(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    if (!av_strcasecmp(arg, "cfr"))         video_sync_method = VSYNC_CFR;
    else if (!av_strcasecmp(arg, "vfr"))         video_sync_method = VSYNC_VFR;
    else if (!av_strcasecmp(arg, "passthrough")) video_sync_method = VSYNC_PASSTHROUGH;
    else if (!av_strcasecmp(arg, "drop"))        video_sync_method = VSYNC_DROP;

    if (video_sync_method == VSYNC_AUTO)
        video_sync_method = parse_number_or_die("vsync", arg, OPT_INT, VSYNC_AUTO, VSYNC_VFR);
    return 0;
}

int opt_timecode(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    char* tcr = av_asprintf("timecode=%s", arg);
    int ret = parse_option(o, "metadata:g", tcr, options);
    if (ret >= 0)
        ret = av_dict_set(&o->g->codec_opts, "gop_timecode", arg, 0);
    av_free(tcr);
    return ret;
}

int opt_channel_layout(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    char layout_str[32];
    char* stream_str;
    char* ac_str;
    int ret, channels, ac_str_size;
    uint64_t layout;

    layout = av_get_channel_layout(arg);
    if (!layout) {
        av_log(NULL, AV_LOG_ERROR, "Unknown channel layout: %s\n", arg);
        return AVERROR(EINVAL);
    }
    snprintf(layout_str, sizeof(layout_str), "%"PRIu64, layout);
    ret = opt_default_new(o, opt, layout_str);
    if (ret < 0)
        return ret;

    /* set 'ac' option based on channel layout */
    channels = av_get_channel_layout_nb_channels(layout);
    snprintf(layout_str, sizeof(layout_str), "%d", channels);
    stream_str = strchr(opt, ':');
    ac_str_size = 3 + (stream_str ? strlen(stream_str) : 0);
    ac_str = av_mallocz(ac_str_size);
    if (!ac_str)
        return AVERROR(ENOMEM);
    av_strlcpy(ac_str, "ac", 3);
    if (stream_str) {
        av_strlcat(ac_str, stream_str, ac_str_size);
    }
    ret = parse_option(o, ac_str, layout_str, options);
    av_free(ac_str);

    return ret;
}

int opt_audio_qscale(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    return parse_option(o, "q:a", arg, options);
}

int opt_filter_complex(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    auto filtergraphs = o->filtergraphs;
    auto& nb_filtergraphs = o->nb_filtergraphs;
    GROW_ARRAY(filtergraphs, nb_filtergraphs);
    if (!(filtergraphs[nb_filtergraphs - 1] = (FilterGraph*)av_mallocz(sizeof(*filtergraphs[0])))) {
        return AVERROR(ENOMEM);
    }
    filtergraphs[nb_filtergraphs - 1]->index      = nb_filtergraphs - 1;
    filtergraphs[nb_filtergraphs - 1]->graph_desc = av_strdup(arg);
    if (!filtergraphs[nb_filtergraphs - 1]->graph_desc) {
        return AVERROR(ENOMEM);
    }

    o->pffmpeg->input_stream_potentially_available = 1;
    return 0;
}

int opt_filter_complex_script(void* optctx, const char* opt, const char* arg) {
    uint8_t* graph_desc = read_file(arg);
    if (!graph_desc) {
        return AVERROR(EINVAL);
    }
    auto o = static_cast<OptionsContext*>(optctx);
    auto filtergraphs = o->filtergraphs;
    auto& nb_filtergraphs = o->nb_filtergraphs;
    GROW_ARRAY(filtergraphs, o->nb_filtergraphs);
    if (!(filtergraphs[nb_filtergraphs - 1] = (FilterGraph*)av_mallocz(sizeof(*filtergraphs[0])))) {
        return AVERROR(ENOMEM);
    }
    filtergraphs[nb_filtergraphs - 1]->index      = nb_filtergraphs - 1;
    filtergraphs[nb_filtergraphs - 1]->graph_desc = (const char*)graph_desc;

    o->pffmpeg->input_stream_potentially_available = 1;
    return 0;
}

int open_files(OptionGroupList* l, const char* inout, int (*open_file)(OptionsContext*, const char*)) {
    int i, ret;
    for (i = 0; i < l->nb_groups; i++) {
        OptionGroup* g = &l->groups[i];
        OptionsContext o;

        init_options(&o);
        o.g = g;

        ret = parse_optgroup(&o, g);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error parsing options for %s file %s.\n", inout, g->arg);
            return ret;
        }

        av_log(NULL, AV_LOG_DEBUG, "Opening an %s file: %s.\n", inout, g->arg);
        ret = open_file(&o, g->arg);
        uninit_options(&o);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error opening %s file %s.\n", inout, g->arg);
            return ret;
        }
        av_log(NULL, AV_LOG_DEBUG, "Successfully opened the file.\n");
    }
    return 0;
}

enum OptGroup {
    GROUP_OUTFILE,
    GROUP_INFILE,
};

static const OptionGroupDef groups[] = {
    { "output url", nullptr, OPT_OUTPUT },
    { "input url", "i", OPT_INPUT },
};

int ffmpeg::ffmpeg_parse_options(int argc, char** argv) {
    OptionParseContext octx;
    char error[128];
    int ret;

    memset(&octx, 0, sizeof(octx));

    /* split the commandline into an internal representation */
    ret = split_commandline(&octx, argc, argv, options, groups, FF_ARRAY_ELEMS(groups));
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error splitting the argument list: ");
        goto fail;
    }

    /* apply global options */
    ret = parse_optgroup(NULL, &octx.global_opts);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error parsing global options: ");
        goto fail;
    }

    /* open input files */
    ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error opening input files: ");
        goto fail;
    }

    /* create the complex filtergraphs */
    ret = init_complex_filters();
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n");
        goto fail;
    }

    /* open output files */
    ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error opening output files: ");
        goto fail;
    }

    check_filter_outputs();

fail:
    uninit_parse_context(&octx);
    if (ret < 0) {
        av_strerror(ret, error, sizeof(error));
        av_log(NULL, AV_LOG_FATAL, "%s\n", error);
    }
    return ret;
}

int opt_progress(void* optctx, const char* opt, const char* arg) {
    auto o = static_cast<OptionsContext*>(optctx);
    AVIOContext* avio = nullptr;
    int ret;
    if (!strcmp(arg, "-")) {
        arg = "pipe:";
    }
    ret = avio_open2(&avio, arg, AVIO_FLAG_WRITE, &o->pffmpeg->int_cb, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Failed to open progress URL \"%s\": %s\n", arg, av_err2str(ret));
        return ret;
    }
    o->pffmpeg->progress_avio = avio;
    return 0;
}
